﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Data.SQLite;
using MySql.Data.MySqlClient;
using Urd.Common;
using Urd.Packet;
namespace Urd.Common
{
    public class better_cached_manager
    {

        struct clientinfo 
        {
            public TcpClient client;
            public NetworkStream stream;
            public byte[] buffer;
            public packet p;
            public clientinfo(TcpClient c,NetworkStream ns ,byte[] b,packet p) 
            {
                client = c;
                stream = ns;
                buffer = b;
                this.p = p;
            }
        }

        public delegate packet Process(cache c,packet p = null);

        #region fields

        //logic
        System.Globalization.CultureInfo customCulture;

        protected string managername, tablename;
        public string Name { get { return managername; } }
        public string Table { get { return tablename; } }
        string tableshema;
        List<string> columnnames;
        protected Process process;

        bool working, listen;
        public bool Working { get { return working; } }
        Thread /*worker,*/ netthread, thread_process_client;//thread_write, thread_read;
        System.Threading.Timer timer, timer2;

        cache sqlcache;
        public cache Cache { get { return sqlcache; } }
        //logic

        //net
        protected IPAddress address;
        public string Address { get { return address.ToString(); } }

        protected int port;
        public string Port { get { return port.ToString(); } }

        //List<clientinfo> clients;
        //List<NetworkStream> streams;
        int pps;
        public string PacketPerSecond { get { return pps.ToString(); ; } }
        TcpListener listener;
        //-net

        //
        protected Opcode opcode_to_accept;
        public string Code { get { return opcode_to_accept.ToString(); } }

        private static ManualResetEvent writeDone =
            new ManualResetEvent(true);
        private static ManualResetEvent readDone =
            new ManualResetEvent(false);

        List<packet> asq,snt;
        

        #endregion

        public void stop()
        {
            working = false;
            stop_cache();
            stop_network();


            columnnames = null;


        }
        public void start()
        {
            asq = new List<packet>();
            snt = new List<packet>();

            pps = 0;
            columnnames = new List<string>();

            ///
            start_network();

            ///
            start_cache(false);

            timer2 = new Timer((object o) => { pps = 0; }, null, 10000, 10000);
            working = true;
            //register in manager list here
        }

        public better_cached_manager(managerinfo info, Process work, string tablename = "", string SQLshema = "")
        {
            customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone();
            customCulture.NumberFormat.NumberDecimalSeparator = ".";
            opcode_to_accept = info.able_to_accept;
            managername = info.managername;
            address = info.address;
            port = info.port;
            process = work;
            this.tablename = tablename;
        }
        public better_cached_manager()
        {
            customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone();
            customCulture.NumberFormat.NumberDecimalSeparator = ".";
            opcode_to_accept = Opcode.Undefined;
            managername = "";
            address = IPAddress.Any;
            port = 0;
            process = null;
            tablename = "";
        }

        #region network funk

        private void start_network()
        {
            try
            {
               // clients = new List<clientinfo>();
                //streams = new List<NetworkStream>();
                listener = new TcpListener(address, port);
                listener.Start();
                listen = true;
                netthread = new Thread(new ThreadStart(acceptClients));
                netthread.Name = managername + " network listen thread";
                netthread.Start();
                
                
               // thread_write = new Thread(new ThreadStart(thread_write_work));
               // thread_write.Name = managername + " network write thread";
               // thread_write.Start();
               // thread_read = new Thread(new ThreadStart(thread_read_work));
               // thread_read.Name = managername + " network read thread";
               // thread_read.Start();

            }
            catch (Exception e) { debug.Log("[" + managername + "][network]:" + e.Source + " " + e.Message, debug.loglevel.Errors); }
        }

        private void stop_network()
        {
            if (listener != null)
            {
                listen = false;
                netthread.Abort();
                listener.Stop();
                listener = null;
            }
        }
       
        void acceptClients()
        {

            if (!listen)
            {
              listener.Stop();
            }
            while (listen)
            {
             
                if (listener.Pending())
                {
                    
                    listener.BeginAcceptTcpClient(accept_callback, listener);

                }
            }
        }

        void accept_callback(IAsyncResult res)
        {
            TcpListener l = (TcpListener)res.AsyncState;
            TcpClient c = l.EndAcceptTcpClient(res);
            thread_process_client = new Thread(new ParameterizedThreadStart(thread_process_clients));
            thread_process_client.Name = managername + " client process thread";
            thread_process_client.Start(c);
        }
        void thread_process_clients(object o)
        {
            TcpClient c = (TcpClient)o;
            NetworkStream ns = c.GetStream();
            packet p = null;
            byte[] buf = new byte[512];
            clientinfo cinfo = new clientinfo(c, ns, buf, p);
            try
            {
                //если есть что читать читаем
                if (c.Available > 0)
                {
                    if (ns.CanRead)
                    {

                        ns.BeginRead(buf, 0, c.Available, read_callback, cinfo);


                    }
                }
                else
                {
                    //в любом случае выполняем делегат даже если пакета нет.

                    cinfo.p = process(sqlcache, cinfo.p);
                    cinfo.stream.BeginWrite(cinfo.p.Bytes, 0, cinfo.p.Bytes.Length, write_callback, cinfo);

                }
            }
            catch 
            {
                ns.Close();
                c.Close();
            }
        }


        void read_callback(IAsyncResult res) 
        {
            try
            {
                clientinfo cinfo = (clientinfo)res.AsyncState;
                int count = cinfo.stream.EndRead(res);
                cinfo.p = new packet(cinfo.buffer);

                cinfo.p = process(sqlcache, cinfo.p);
                cinfo.stream.BeginWrite(cinfo.p.Bytes, 0, cinfo.p.Bytes.Length, write_callback, cinfo);
            }
            catch { }
            
            //readDone.Set();
        }


        void write_callback(IAsyncResult res)
        {
            clientinfo cinfo = (clientinfo)res.AsyncState;
            cinfo.stream.EndWrite(res);
            cinfo.stream.Close();
            cinfo.client.Close();
           
           // writeDone.Set();
            
        }

        //void thread_read_work() 
        //{
        //    while (true)
        //    {
        //        cleanupDone.WaitOne();
        //        readDone.Reset();
                
        //        for (int i = 0; i < clients.Count; i++)
        //        {
        //            try
        //            {
        //                if (clients[i].client.Connected)
        //                {
        //                    NetworkStream ns = clients[i].client.GetStream();

        //                    if (ns.DataAvailable)
        //                    {
        //                        // lock (sqlcache)
        //                        //{
        //                        // NetworkStream ns = clients[i].stream;
        //                        byte[] buffer = new byte[127];
        //                        ns.Read(buffer, 0, buffer.Length);
        //                        packet p = new packet(buffer);
        //                        clientinfo c = new clientinfo();
        //                        packet pp = process(p, sqlcache);

        //                        c.buffer = buffer;
        //                        c.client = clients[i].client;
        //                        c.tosent = pp;
        //                        c.asq = p;
        //                        c.stream = ns;
        //                        clients[i] = c;
                                

        //                        // }
        //                    }
        //                    ns.Close();
        //                    clients[i].client.Close();
        //                }
        //            }
        //            catch (Exception e)
        //            {
        //                debug.Log(e.Message, debug.loglevel.Warnings);

        //            }
        //            finally 
        //            {
        //                //clients[i].stream.Close();
        //               // if(!clients[i].client.Connected) clients[i].client.Close();
        //                //clients[i] = new clientinfo(null, null, null, null);
        //            }
        //        }
        //        readDone.Set();
        //        Thread.Sleep(4);
        //    }
            
        //}
        //void thread_write_work() 
        //{
        //    while (true) 
        //    {
        //        cleanupDone.WaitOne();
        //        readDone.WaitOne();
        //        for (int i = 0; i < clients.Count; i++)
        //        {
        //            try
        //            {

        //                if (clients[i].client.Connected)
        //                {
        //                    if (clients[i].tosent != null)
        //                    {
                                
        //                        NetworkStream ns = clients[i].client.GetStream();
        //                        if (ns.CanWrite)
        //                        {
        //                            //ns.BeginWrite(clients[i].tosent.Bytes, 0, 0,write_callback,ns);
        //                            byte[] buf = clients[i].tosent.Bytes;
        //                            foreach (byte b in buf)
        //                            {
        //                                ns.WriteByte(b);
        //                            }
        //                        }
        //                        //snt.Add(clients[i].currentpacket);
        //                        //Thread.Sleep(200);
                               
        //                    }

        //                }
                        

                        
        //            }
        //            catch (Exception e)
        //            {
        //                debug.Log(e.Message, debug.loglevel.Warnings);

        //            }
        //            finally 
        //            {
        //                //clients[i].stream.Close();
        //                if (!clients[i].client.Connected) clients[i].client.Close();
        //                //clients[i] = new clientinfo(null, null, null, null);
        //            }
        //            Thread.Sleep(3);
                    
        //        }


        //        Thread.Sleep(40);
        //    }
        //}
   
        #endregion

        #region cache funk
        public void dump()
        {
            if (!System.IO.Directory.Exists(config.tablesfolder)) System.IO.Directory.CreateDirectory(config.tablesfolder);
            sqlcache.DumpTable(config.tablesfolder + tablename + ".db");
        }
        private void start_cache(bool take_from_mysql)
        {
            if (tablename == "") tablename = managername;
            sqlcache = new cache();
            if (take_from_mysql)
            {
                prepare_shema();
                prepare_cache(true);
            }
            else
            {
                string filename = config.tablesfolder + tablename + ".db";
                SQLiteConnection con = new SQLiteConnection("Data Source=" + filename + ";Version=3;New=True;");
                con.Open();
                con.BackupDatabase(sqlcache.Connection, "main", "main", -1, null, -1);
                con.Close();
            }
            timer = new System.Threading.Timer(new TimerCallback(sync), null, 4000, 4000);
        }
        private void stop_cache()
        {
            if (sqlcache != null)
            {
                timer.Dispose();
                sqlcache.close();
                sqlcache = null;
            }
        }
        private void prepare_shema()
        {

            string database_schema = "";

            object[] schema_unformatted
                = mysql.Sql("show create table " + tablename, true);

            database_schema = schema_unformatted[1] as string;

            string pattern = @"`\w+`";

            MatchCollection matches = Regex.Matches(database_schema, pattern);

            foreach (Match m in matches)
            {
                string val = m.Value.Replace("`", "");
                if (!columnnames.Contains(val)) columnnames.Add(val);
            }

            int end = database_schema.LastIndexOf(')') + 2;

            database_schema = database_schema.Remove(end);
            database_schema = database_schema.Replace("unsigned", " ");
            database_schema = database_schema.Replace("AUTO_INCREMENT", "");

            tableshema = database_schema;


        }

        private void prepare_cache(bool backup)
        {
            try
            {
                System.Threading.Thread.CurrentThread.CurrentCulture = customCulture;
                //here we can backup create table info
                if (backup)
                {
                    if (!System.IO.Directory.Exists(config.shemasfolder)) System.IO.Directory.CreateDirectory(config.shemasfolder);
                    System.IO.File.WriteAllText(config.shemasfolder + tablename + ".sql", tableshema);
                }
                sqlcache.Sql(tableshema, false);

                int max = 0;
                object ret = mysql.Sql("select max(id) from " + tablename, true)[0];
                if (ret.GetType() != typeof(System.DBNull))
                {
                    max = (int)ret;
                    for (int i = 0; i <= max; i++)
                    {
                        string values = "";
                        object[] val = mysql.Sql("select * from " + tablename + " where id=" + i.ToString(), true);
                        int l = val.Length;
                        for (int c = 0; c < l; c++)
                        {
                            if (val[c] != null)
                            {
                                string temp = val[c].ToString();
                                if (temp == "") temp = "null";
                                int ii;
                                float ff;
                                if (!Int32.TryParse(temp, out ii) && !Single.TryParse(temp, out ff) && temp != "null") temp = "'" + temp + "'";
                                values += temp;
                                if (c < l - 1) values += ",";
                            }
                        }
                        if (values != "")
                            sqlcache.Sql("insert into " + tablename + " values(" + values + ")", false);
                    }
                }
            }
            catch (MySqlException e) { Urd.Common.debug.Log("[prepare_cache][cache][mysql]:" + e.Source + " " + e.Message, Urd.Common.debug.loglevel.Errors); }
            catch (SQLiteException e) { Urd.Common.debug.Log("[prepare_cache][cache][sqlite]:" + e.Source + " " + e.Message, Urd.Common.debug.loglevel.Errors); }
            catch (Exception e) { Urd.Common.debug.Log("[prepare_cache][cache][system]:" + e.Source + " " + e.Message, Urd.Common.debug.loglevel.Errors); }


        }
        private void sync(object o)
        {
            try
            {
                System.Threading.Thread.CurrentThread.CurrentCulture = customCulture;

                string query = "";
                long max = 0;
                object[] temp = sqlcache.Sql("select max(id) from " + tablename, true);

                if (temp[0].GetType() != typeof(System.DBNull))
                {
                    max = (long)temp[0];
                    long min = (long)sqlcache.Sql("select max(id) from " + tablename, true)[0];
                    for (long i = min; i <= max; i++)
                    {
                        string askquery = "";

                        for (int cc = 1; cc < columnnames.Count; cc++)
                        {
                            askquery += "'" + columnnames[cc] + "'";

                            if (cc < columnnames.Count - 1) askquery += ",";
                        }
                        object[] val = sqlcache.Sql("select * from " + tablename + " where id=" + i.ToString(), true);
                        int l = val.Length;
                        if (l == columnnames.Count - 1)
                        {
                            foreach (object ob in val) { if (ob == null) return; }

                            query += "update ";
                            query += columnnames[0];
                            query += " set ";
                            for (int cc = 0; cc < val.Length; cc++)
                            {
                                if (!(val[cc].GetType() == typeof(DBNull)))
                                {
                                    query += columnnames[cc + 1] + " = '" + val[cc] + "'";
                                    if (cc < val.Length - 1) query += " , ";
                                }
                            }



                            query += " where id=" + val[0].ToString() + ";";
                        }
                        //cache.Sql("insert into pos values(" + values + ")", false);
                        if (query != "") mysql.Sql(query, false);
                    }

                }

            }
            catch (MySqlException e) { Urd.Common.debug.Log("[cache][mysql]:" + e.Source + " " + e.Message, Urd.Common.debug.loglevel.Errors); }
            catch (SQLiteException e) { Urd.Common.debug.Log("[cache][sqlite]:" + e.Source + " " + e.Message, Urd.Common.debug.loglevel.Errors); }
            catch (Exception e) { Urd.Common.debug.Log("[cache][system]:" + e.Source + " " + e.Message, Urd.Common.debug.loglevel.Errors); }
        }

        #endregion
    }
}


